package com.atmosphere.component;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

import com.atmosphere.component.base.AbstractAstronomy;
import com.atmosphere.interfaces.IAtmosphere;
import com.atmosphere.interfaces.IComponentKey;
import com.atmosphere.interfaces.component.ILightingComponent;
import com.atmosphere.util.TimeConverter;
import com.jme.light.DirectionalLight;
import com.jme.math.Vector3f;
import com.jme.renderer.ColorRGBA;
import com.jme.scene.Node;
import com.jme.scene.state.LightState;
import com.jme.system.DisplaySystem;

/**
 * <code>LightingComponent</code> defines the implementation of
 * the component unit that is responsible for providing the logic
 * of animation an astronomy light.
 *
 * @author Yi Wang (Neakor)
 * @version Creation date: 05-20-2009 6:34:46 PM EST
 * @version Modified date: 05-20-2009 6:34:46 PM EST
 */
public class LightingComponent extends AbstractAstronomy implements ILightingComponent {
	/**
	 * The <code>TimeConverter</code> instance.
	 */
	private final TimeConverter converter;
	/**
	 * The update <code>Lock</code>.
	 */
	private final Lock lock;
	/**
	 * The <code>LightState</code> instance.
	 */
	private final LightState state;
	/**
	 * The array of <code>Float</code> of day color.
	 */
	private final float[] day;
	/**
	 * The array of <code>Float</code> of night color.
	 */
	private final float[] night;
	/**
	 * The array of <code>Float</code> of transition color.
	 */
	private final float[] transition;
	/**
	 * The maintained <code>DirectionalLight</code>.
	 */
	private DirectionalLight light;
	/**
	 * The parent <code>Node</code>.
	 */
	private Node parent;
	/**
	 * The <code>IAtmosphere</code> system.
	 */
	private IAtmosphere atmosphere;
	/**
	 * The virtual position <code>Node</code>.
	 */
	private Node virtualNode;
	/**
	 * The update global ambient <code>Boolean</code> flag.
	 */
	private volatile boolean updateAmbient;
	/**
	 * The temporary <code>Vector3f</code> light direction.
	 */
	private final Vector3f tempDirection;
	/**
	 * The temporary color float array for update.
	 */
	private final float[] tempColor;
	/**
	 * The temporary global ambient <code>ColorRGBA</code>.
	 */
	private final ColorRGBA tempAmbient;

	/**
	 * Constructor of <code>LightingComponent</code>.
	 * @param key The <code>IComponentKey</code>.
	 */
	public LightingComponent(IComponentKey key) {
		this(key, DisplaySystem.getDisplaySystem().getRenderer().createLightState());
	}

	/**
	 * Constructor of <code>LightingComponent</code>.
	 * @param key The <code>IComponentKey</code>.
	 * @param state The <code>LightState</code> instance.
	 */
	public LightingComponent(IComponentKey key, LightState state) {
		super(key);
		if(state == null) throw new IllegalArgumentException("Light state cannot be null.");
		this.state = state;
		this.converter = new TimeConverter();
		this.lock = new ReentrantLock();
		this.day = new float[4];
		this.night = new float[4];
		this.transition = new float[4];
		this.tempDirection = new Vector3f();
		this.tempColor = new float[4];
		this.tempAmbient = new ColorRGBA(1,1,1,1);
	}

	@Override
	public void initialize(Node parent, IAtmosphere atmosphere) {
		this.parent = parent;
		this.atmosphere = atmosphere;
		this.initLight();
		this.initVirualNode();
		this.initRotation();
		this.parent.attachChild(this.node);
	}

	/**
	 * Initialize the light.
	 */
	private void initLight() {
		this.light = new DirectionalLight();
		this.light.setEnabled(true);
		this.light.setDiffuse(this.getNightColor());
		this.light.setSpecular(new ColorRGBA(1,1,1,1));
		this.light.setAttenuate(false);
		this.light.setShadowCaster(true);
		this.state.setEnabled(true);
		this.state.attach(this.light);
	}

	/**
	 * Initialize the virtual animated node.
	 */
	private void initVirualNode() {
		this.virtualNode = new Node("VirtualPosition");
		this.virtualNode.getLocalTranslation().set(this.position);
		this.node.attachChild(this.virtualNode);
	}

	@Override
	public void update(float interpolation) {
		this.updateDirection();
		// Get clock time.
		final float clocktime = this.converter.animToClock(this.animator.getCurTime(), false, this.animator.getMaxTime());
		// Update color.
		this.updateColor(clocktime);
		if(this.updateAmbient) this.updateAmbientColor();
	}

	/**
	 * Update the lighting direction.
	 */
	private void updateDirection() {
		this.tempDirection.set(this.parent.getWorldTranslation());
		this.tempDirection.subtractLocal(this.virtualNode.getWorldTranslation());
		this.tempDirection.normalizeLocal();
		this.light.getDirection().set(this.tempDirection);
	}

	/**
	 * Update the color blending based on given clock time.
	 * @param clocktime The <code>Float</code> clock time.
	 */
	private void updateColor(float clocktime) {
		// Day time.
		if(clocktime >= this.atmosphere.getDayStart() && clocktime < this.atmosphere.getDayEnd()) {
			for(int i = 0; i < this.tempColor.length; i++) {
				this.tempColor[i] = this.day[i];
			}
			this.setColor(this.light.getDiffuse());
		// Else if the time is between day-end and day-transition, change towards transition color.
		} else if(clocktime >= this.atmosphere.getDayEnd() && clocktime < this.atmosphere.getDayTransition()) {
			for(int i = 0; i < this.tempColor.length; i++) {
				this.tempColor[i] = this.day[i] +
					(this.transition[i]-this.day[i])*(clocktime-this.atmosphere.getDayEnd())/this.atmosphere.getDiffTransDay();
			}
			this.setColor(this.light.getDiffuse());
		// Else if the time is between day-transition and night-start, change towards night color.
		} else if(clocktime >= this.atmosphere.getDayTransition() && clocktime < this.atmosphere.getNightStart()) {
			for(int i = 0; i < this.tempColor.length; i++) {
				this.tempColor[i] = this.transition[i] +
					(this.night[i]-this.transition[i])*(clocktime-this.atmosphere.getDayTransition())/this.atmosphere.getDiffNightTrans();
			}
			this.setColor(this.light.getDiffuse());
		// Night time.
		} else if(clocktime >= this.atmosphere.getNightStart() || clocktime < this.atmosphere.getNightEnd()) {
			for(int i = 0; i < this.tempColor.length; i++) {
				this.tempColor[i] = this.night[i];
			}
			this.setColor(this.light.getDiffuse());
		// Else if the time is between night-end and night-transition, change towards transition color.
		} else if(clocktime >= this.atmosphere.getNightEnd() && clocktime < this.atmosphere.getNightTransition()) {
			for(int i = 0; i < this.tempColor.length; i++) {
				this.tempColor[i] = this.night[i] +
					(this.transition[i]-this.night[i])*(clocktime-this.atmosphere.getNightEnd())/this.atmosphere.getDiffTransNight();
			}
			this.setColor(this.light.getDiffuse());
		// If the time is between night-transition and day-start, change towards day color.
		} else if(clocktime >= this.atmosphere.getNightTransition() && clocktime < this.atmosphere.getDayStart()) {
			for(int i = 0; i < this.tempColor.length; i++) {
				this.tempColor[i] = this.transition[i] +
					(this.day[i]-this.transition[i])*(clocktime-this.atmosphere.getNightTransition())/this.atmosphere.getDiffDayTrans();
			}
			this.setColor(this.light.getDiffuse());
		}
	}
	
	/**
	 * Update the ambient light color based on the sunlight and moonlight colors.
	 */
	private void updateAmbientColor() {
		this.tempColor[0] = this.light.getDiffuse().r;
		this.tempColor[1] = this.light.getDiffuse().g;
		this.tempColor[2] = this.light.getDiffuse().b;
		this.tempColor[3] = 1;
		this.setColor(this.tempAmbient);
		this.light.setAmbient(this.tempAmbient);
		this.state.setGlobalAmbient(this.tempAmbient);
	}

	/**
	 * Set the given color to the temporary color.
	 * @param color The <code>ColorRGBA</code> to be set.
	 */
	private void setColor(ColorRGBA color) {
		color.r = this.tempColor[0];
		color.g = this.tempColor[1];
		color.b = this.tempColor[2];
		color.a = this.tempColor[3];
		color.clamp();
	}

	@Override
	public void setDayColor(ColorRGBA color) {
		this.lock.lock();
		try {
			this.day[0] = color.r;
			this.day[1] = color.g;
			this.day[2] = color.b;
			this.day[3] = color.a;
		} finally {
			this.lock.unlock();
		}
	}

	@Override
	public void setNightColor(ColorRGBA color) {
		this.lock.lock();
		try {
			this.night[0] = color.r;
			this.night[1] = color.g;
			this.night[2] = color.b;
			this.night[3] = color.a;
		} finally {
			this.lock.unlock();
		}
	}

	@Override
	public void setTransitionColor(ColorRGBA color) {
		this.lock.lock();
		try {
			this.transition[0] = color.r;
			this.transition[1] = color.g;
			this.transition[2] = color.b;
			this.transition[3] = color.a;
		} finally {
			this.lock.unlock();
		}
	}

	@Override
	public void setUpdateAmbient(boolean update) {
		this.updateAmbient = update;
	}

	@Override
	public LightState getLightState() {
		return this.state;
	}

	@Override
	public ColorRGBA getDayColor() {
		return new ColorRGBA(this.day[0], this.day[1], this.day[2], this.day[3]);
	}

	@Override
	public ColorRGBA getNightColor() {
		return new ColorRGBA(this.night[0], this.night[1], this.night[2], this.night[3]);
	}

	@Override
	public ColorRGBA getTransitionColor() {
		return new ColorRGBA(this.transition[0], this.transition[1], this.transition[2], this.transition[3]);
	}

	@Override
	public void cleanup() {}
}
